package com.xlh.virtualization.websocket;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.xlh.enums.user.RoleEnum;
import com.xlh.service.course.ChapterExtendService;
import com.xlh.support.experimentshare.domain.ShareUser;
import com.xlh.support.experimentshare.service.ExperimentShareService;
import com.xlh.util.SpringContextUtil;
import com.xlh.virtualization.domain.ContainerContext;
import com.xlh.virtualization.domain.ExperimentContext;
import com.xlh.virtualization.job.delayed.StopContainerListener;
import com.xlh.virtualization.job.delayed.StopContainerTask;
import com.xlh.virtualization.service.ExperimentStatisticService;
import com.xlh.virtualization.websocket.message.FlavorUpdateMessage;
import com.xlh.websocket.GetHttpSessionConfigurator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

import static com.xlh.virtualization.constant.ContextTypeEnum.COURSE;
import static com.xlh.virtualization.constant.ContextTypeEnum.TASK;

/**
 * 课程实验WebSocket，用于记录实验时长和停止容器
 *
 * @author cheer
 */
@ServerEndpoint(value = "/ws/experiment/course/{chapterId}", configurator = GetHttpSessionConfigurator.class)
@Component
@Slf4j
public class CourseExperimentWebsocket {

    private static final ExperimentStatisticService experimentStatisticService = SpringContextUtil.getBean(ExperimentStatisticService.class);

    private static final ExperimentShareService experimentShareService = SpringContextUtil.getBean(ExperimentShareService.class);

    private static final ChapterExtendService chapterExtendService = SpringContextUtil.getBean(ChapterExtendService.class);

    private static final ThreadPoolTaskScheduler taskScheduler = SpringContextUtil.getBean(ThreadPoolTaskScheduler.class);

    private static final ThreadPoolTaskExecutor taskExecutor = SpringContextUtil.getBean(ThreadPoolTaskExecutor.class);

    private static Multimap<ContainerContext, CourseExperimentWebsocket> containerContextMap = HashMultimap.create();

    private static Multimap<ExperimentContext, CourseExperimentWebsocket> experimentContextMap = HashMultimap.create();

    /**
     * 存储计时 userId, chapterId, date
     */
    private static Table<Long, Long, Date> startTimeMap = HashBasedTable.create();

    private Session session;

    private ContainerContext containerContext;

    private ExperimentContext experimentContext;

    /**
     * 是否为学生
     */
    private Boolean isStudent;

    @OnOpen
    public void onOpen(Session session, @PathParam("chapterId") Long chapterId, EndpointConfig config) {
        this.session = session;
        Long userId = (Long) config.getUserProperties().get("userId");
        this.isStudent = Objects.equals(RoleEnum.STUDENT.getRoleCode(), config.getUserProperties().get("roleId"));

        Map<String, Object> map = chapterExtendService.getCourseInfo(chapterId);
        if (map == null || map.get("courseImageLoaded") == null) {
            log.error("任务id没有对应的课程信息");
            return;
        }

        if ((boolean) map.get("courseImageLoaded")) {
            this.containerContext = new ContainerContext()
                    .setUserId(userId)
                    .setContextType(COURSE.getCode())
                    .setContextId((Long) map.get("courseId"));
        } else {
            this.containerContext = new ContainerContext()
                    .setUserId(userId)
                    .setContextType(TASK.getCode())
                    .setContextId(chapterId);
        }

        this.experimentContext = new ExperimentContext()
                .setUserId(userId)
                .setContextType(TASK.getCode())
                .setContextId(chapterId);

        synchronized (CourseExperimentWebsocket.class) {
            containerContextMap.put(containerContext, this);
            experimentContextMap.put(experimentContext, this);

            if (isStudent && !startTimeMap.contains(userId, chapterId)) {
                startTimeMap.put(userId, chapterId, new Date());
            }
        }
    }

    @OnClose
    public void onClose() {
        synchronized (CourseExperimentWebsocket.class) {
            experimentContextMap.remove(experimentContext, this);

            if (!experimentContextMap.containsKey(experimentContext)) {
                // 计时统计
                if (isStudent) {
                    Date startTime = startTimeMap.remove(experimentContext.getUserId(), experimentContext.getContextId());
                    Date endTime = new Date();

                    taskExecutor.execute(() -> experimentStatisticService.saveExperimentDuration(
                            experimentContext.getUserId(), TASK.getCode(), experimentContext.getContextId(),
                            startTime, endTime));
                }

                // 关闭分享
                if (TASK.getCode().equals(experimentContext.getContextType())) {
                    taskScheduler.schedule(() -> {
                        if (!experimentContextMap.containsKey(experimentContext)) {
                            ShareUser shareUser = ShareUser.builder()
                                    .userId(experimentContext.getUserId())
                                    .chapterId(experimentContext.getContextId())
                                    .build();
                            experimentShareService.closeShareExperiment(shareUser);
                            experimentShareService.invalidShareExperiment(shareUser);
                        }
                    }, new Date(System.currentTimeMillis() + 5000));
                }

                containerContextMap.remove(containerContext, this);

                if (!containerContextMap.containsKey(containerContext)) {
                    StopContainerTask task = new StopContainerTask(containerContext);
                    StopContainerListener.putTask(task);
                }
            }

        }
    }

    @OnMessage
    public void onMessage(String jsonStr, Session session) {
        // 暂时不需要实现该方法
    }

    @OnError
    public void onError(Session session, Throwable error) {
        // 暂时不需要实现该方法
    }

    /**
     * 通知配置更新
     *
     * @param chapterId 章节Id
     */
    public static void notifyUpdateFlavor(Long chapterId) {
        FlavorUpdateMessage flavorUpdateMessage = FlavorUpdateMessage.builder().build();
        String message = JSONObject.toJSONString(flavorUpdateMessage);

        for (Map.Entry<ExperimentContext, Collection<CourseExperimentWebsocket>> entry : experimentContextMap.asMap().entrySet()) {
            if (Objects.equals(entry.getKey().getContextId(), chapterId)) {
                for (CourseExperimentWebsocket websocket : entry.getValue()) {
                    try {
                        websocket.session.getBasicRemote().sendText(message);
                    } catch (IOException e) {
                        log.warn("websocket send message exception", e);
                    }
                }
            }
        }
    }

    public static Boolean isOnline(ContainerContext containerContext) {
        return containerContextMap.containsKey(containerContext);
    }
}
